/*
 * IteratorSafeCollection.java
 *
 * Created on November 14, 2006, 7:01 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package edu.umn.cs5115.scheduler.framework;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * This is a class that supports modifying while iterating through it, because
 * it actually keeps track of changes to it while you iterate through it, then
 * makes those changes when you let it know you're done.
 * @author grant
 */
public class IteratorSafeSet<T> implements Set<T>
{
    /** List of items currently listening to this key. */
    // TODO: Compare performance of iterating over a hash set with iterating over an ArrayList.
    // I suspect that if it differes, an ArrayList would actually be the better choice here.
    private Collection<T> items = new HashSet();
    /** Items that were added while the key was having a notification sent on it.*/
    private Collection<T> addedItems = new HashSet();
    /** Items that were removed while the key was having a notification sent. */
    private Collection removedItems = new HashSet();
    /** number of times iterations have been recursively started on this key. */
    private int iterationLevel;

    /**
     * Add a item to the list of items.  If there is a
     * iteration in progress, the item will not be added until there
     * are no more iterations being done.
     * @param item The item to add.
     */
    public boolean add(T item)
    {
        if (iterationLevel <= 0)
        {
            if (!items.contains(item))
            {
                items.add(item);
                return true;
            } else
                return false;
        } else {
            if (removedItems.contains(item))
                removedItems.remove(item);
            if (!addedItems.contains(item))
            {
                addedItems.add(item);
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * Remove an item from the list of items for this key.  If there
     * is an iteration in progress, the item will not be removed until
     * there are no more iterations being done.
     * @param item The item to (eventually) remove.
     */
    public boolean remove(Object item)
    {
        if (iterationLevel <= 0)
        {
            return items.remove(item);
        } else {
            boolean returnValue = false;
            if (addedItems.contains(item))
            {
                addedItems.remove(item);
                returnValue = true;
            }
            if (!removedItems.contains(item))
            {
                removedItems.add(item);
                if (!returnValue)
                    returnValue = items.contains(item);
            }
            return returnValue;
        }
    }

    /**
     * Call this method before calling iterator() to iterate through the values
     * for the collection. Once you have called this method, match it with 
     * finishIterating().  The number of calls must be equivalent.
     * @see #finishIterating()
     */
    public void startIterating()
    {
        iterationLevel++;
    }

    public void finishIterating()
    {
        if (iterationLevel > 0)
        {
            iterationLevel--;
            if (iterationLevel <= 0)
            {
                items.addAll(addedItems);
                items.removeAll(removedItems);
            }
        } else {
            throw new IllegalStateException("didSendNotifications called without matching call to willSendNotifications");
        }
    }

    public int size()
    {
        if (iterationLevel <= 0)
            return items.size();
        else {
            HashSet itemsInSet = new HashSet(items);
            itemsInSet.removeAll(removedItems);
            itemsInSet.addAll(addedItems);
            return itemsInSet.size();
        }
    }
    
    /**
     * Calculate the size of the set, after any pending removals/additions are 
     * processed.
     * @return The size the set will become if all iterators stopped without
     * any more modifications to the set.
     */
    public int actualSize()
    {
        if (iterationLevel > 0)
        {
            HashSet copy = new HashSet(items);
            copy.addAll(addedItems);
            copy.removeAll(removedItems);
            return copy.size();
        } else {
            return size();
        }
    }

    public boolean isEmpty()
    {
        return size()==0;
    }

    public boolean contains(Object o)
    {
        return items.contains(o);
    }

    public Iterator<T> iterator()
    {
        return items.iterator();
    }

    public Object[] toArray()
    {
        return items.toArray();
    }

    public <T> T[] toArray(T[] array)
    {
        return items.toArray(array);
    }

    public boolean containsAll(Collection<?> collection)
    {
        return items.containsAll(collection);
    }

    public boolean addAll(Collection<? extends T> collection)
    {
        boolean modified = false;
        for(T thisItem : collection)
        {
            if (add(thisItem))
                modified = true;
        }
        return modified;
    }

    public boolean removeAll(Collection<?> collection)
    {
        boolean modified = false;
        for(Object thisItem : collection)
        {
            if (remove(thisItem))
                modified = true;
        }
        return modified;
    }

    public boolean retainAll(Collection<?> collection)
    {
        if (iterationLevel > 0)
        {
            boolean modified = false;
            for(T thisItem : items)
            {
                if (!collection.contains(thisItem))
                {
                    remove(thisItem);
                }
            }
            return addedItems.retainAll(collection) || modified;
        } else {
            return items.retainAll(collection);
        }
    }

    public void clear()
    {
        if (iterationLevel > 0)
        {
            addedItems.clear();
            removedItems.clear();
            removedItems.addAll(items);
        } else {
            items.clear();
        }
    }
}
